package top.wilsonlv.jaguar.cloud.handlerlog.client.component;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.jms.JmsException;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.lang.NonNull;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import top.wilsonlv.jaguar.cloud.handlerlog.client.HandlerLogConstant;
import top.wilsonlv.jaguar.cloud.handlerlog.client.dto.HandlerLogSaveDTO;
import top.wilsonlv.jaguar.cloud.handlerlog.client.properties.HandlerLogProperties;
import top.wilsonlv.jaguar.commons.web.WebConstant;
import top.wilsonlv.jaguar.commons.web.util.WebUtil;
import top.wilsonlv.jaguar.oauth2.security.Oauth2SecurityConstant;
import top.wilsonlv.jaguar.security.model.SecurityUser;
import top.wilsonlv.jaguar.security.util.SecurityUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.time.LocalDateTime;


/**
 * @author lvws
 * @since 2018/11/12.
 */
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "jaguar.handler-log", name = "enable", havingValue = "true", matchIfMissing = true)
public class HandlerLogInterceptor implements HandlerInterceptor {

    private static final String ERROR = "/error";

    private final JmsTemplate jmsQueueTemplate;

    private final HandlerLogProperties handlerLogProperties;

    @Override
    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
        String requestUri = request.getRequestURI();
        if (handler instanceof HandlerMethod && !ERROR.equals(requestUri)
                && !request.getRequestURI().startsWith(Oauth2SecurityConstant.ACTUATOR_URL_PREFIX)) {
            SecurityUser currentUser = SecurityUtil.getCurrentUser(false);
            if (currentUser != null) {
                log.info("用户ID: {}, 用户名: {}", currentUser.getId(), currentUser.getUsername());
            } else {
                log.info("用户[{}@{}]匿名请求", WebUtil.getHost(request), request.getHeader(HttpHeaders.USER_AGENT));
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(@NonNull final HttpServletRequest request, @NonNull final HttpServletResponse response,
                                @NonNull Object handler, final Exception exception) {
        String requestUri = request.getRequestURI();
        if (handler instanceof HandlerMethod && !ERROR.equals(requestUri)
                && !request.getRequestURI().startsWith(Oauth2SecurityConstant.ACTUATOR_URL_PREFIX)) {
            this.log(request, response, (HandlerMethod) handler, exception);
        }

        HandlerResponseBodyAdvice.remove();
    }

    public HandlerLogSaveDTO log(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
                                 HandlerMethod handler, Exception exception) {
        String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (!StringUtils.hasText(authorization)) {
            authorization = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
        }

        HandlerLogSaveDTO handlerLog = new HandlerLogSaveDTO();
        handlerLog.setAuthorization(authorization);
        handlerLog.setAccessTime((LocalDateTime) request.getAttribute(WebConstant.REQUEST_ACCESS_TIME));
        handlerLog.setClientHost(WebUtil.getHost(request));
        handlerLog.setRequestUri(request.getRequestURI());

        if (handler != null) {
            ApiOperation apiOperation = handler.getMethod().getAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                handlerLog.setApiOperation(apiOperation.value());
            }
        }

        if (handlerLog.getApiOperation() == null && handlerLogProperties.getApiOperations() != null) {
            String apiOperationName = handlerLogProperties.getApiOperations().get(request.getRequestURI());
            handlerLog.setApiOperation(apiOperationName);
        }

        handlerLog.setMethod(request.getMethod());
        handlerLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
        handlerLog.setParameters(JSONObject.toJSONString(request.getParameterMap()));

        HttpSession session = request.getSession(false);
        if (session != null) {
            handlerLog.setSessionId(session.getId());

            SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
            if (securityContext != null && securityContext.getAuthentication() != null) {
                Object principal = securityContext.getAuthentication().getPrincipal();
                if (principal instanceof SecurityUser) {
                    handlerLog.setCreateBy(((SecurityUser) principal).getUsername());
                    handlerLog.setCreateUserId(((SecurityUser) principal).getId());
                }
            }
        }

        SecurityUser currentUser = SecurityUtil.getCurrentUser(false);
        if (currentUser != null) {
            handlerLog.setCreateBy(currentUser.getUsername());
            handlerLog.setCreateUserId(currentUser.getId());
        }

        long duration = Duration.between(handlerLog.getAccessTime(), LocalDateTime.now()).toMillis();
        handlerLog.setDuration(duration);
        handlerLog.setStatus(response.getStatus());
        if (exception != null) {
            handlerLog.setErrorMsg(ExceptionUtil.getMessage(exception));
        }

        Object body = HandlerResponseBodyAdvice.getBody();
        if (body != null) {
            handlerLog.setJsonResult(JSONObject.toJSONString(body));
        }

        try {
            if (log.isDebugEnabled()) {
                log.debug("send handler log");
            }
            jmsQueueTemplate.convertAndSend(HandlerLogConstant.DESTINATION_HANDLER_LOG, handlerLog);
        } catch (JmsException e) {
            log.debug(e.getMessage());
        }

        return handlerLog;
    }

}
